package com.spel.validate.aspect;

import com.spel.validate.AssertExceptionCondition;
import com.spel.validate.AssertAspectCondition;
import com.spel.validate.PermissionExpressionParser;
import com.spel.validate.annotation.BaseAssert;
import com.spel.validate.annotation.BatchAssert;
import com.spel.validate.enums.LogicalEnum;
import com.spel.validate.exception.AssertException;
import com.spel.validate.util.AopUtil;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @title: PermissionAspect
 * @Author xu-xiang
 * @Date: 2024/1/29 16:50
 * @description: 断言aop
 */
@Component
@Aspect
@Order(-1)
public class AssertAspect {

    private static final Logger logger = LoggerFactory.getLogger(AssertAspect.class);

    @Resource
    private PermissionExpressionParser expressionParser;

    @Pointcut(
            "@annotation(com.spel.validate.annotation.HasPermissions) " +
                    "|| @within(com.spel.validate.annotation.HasPermissions) " +
                    "||@annotation(com.spel.validate.annotation.BatchAssert) " +
                    "||@within(com.spel.validate.annotation.BatchAssert) " +
                    "||@annotation(com.spel.validate.annotation.BaseAssert) " +
                    "||@within(com.spel.validate.annotation.BaseAssert) "
    )
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object AssertAround(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Object[] args = joinPoint.getArgs();

        Class<?> proxyBeanClass = AopUtil.getUltimateProxyClass(joinPoint.getTarget());
        // 获取方法级别注解
        BaseAssert baseAssert = AnnotatedElementUtils.getMergedAnnotation(method, BaseAssert.class);
        BatchAssert batchAssert = AnnotatedElementUtils.getMergedAnnotation(method, BatchAssert.class);

        // 获取类级别注解
        if (baseAssert == null && batchAssert == null) {
            baseAssert = AnnotatedElementUtils.getMergedAnnotation(proxyBeanClass, BaseAssert.class);
            batchAssert = AnnotatedElementUtils.getMergedAnnotation(proxyBeanClass, BatchAssert.class);
        }

        LogicalEnum AuthLogical = LogicalEnum.AND;  //默认And
        if (batchAssert != null) {
            AuthLogical = batchAssert.Logical();
        }

        // 读取注解信息并封装
        List<AssertAspectCondition> assertAspectConditionList = this.buildCondition(batchAssert, baseAssert);

        // AND 条件处理, 需满足全部条件
        if (AuthLogical == LogicalEnum.AND) {
            for (AssertAspectCondition condition : assertAspectConditionList) {
                this.validateCondition(method, args, condition);
            }
        } else {
            // OR 条件处理, 满足其中一组条件即可
            Exception hasError = new Exception();
            for (AssertAspectCondition condition : assertAspectConditionList) {
                try {
                    this.validateCondition(method, args, condition);
                    hasError = null;
                    break;
                } catch (AssertException e) {
                    logger.debug("不满足条件:{}", ExceptionUtils.getStackTrace(e));
                    hasError = e;
                }
            }

            if (hasError != null) {
                throw hasError;
            }
        }

        return joinPoint.proceed();
    }

    /**
     * 验证条件是否成立
     */
    private void validateCondition(Method method, Object[] args, AssertAspectCondition condition) {
        List<String> groupList = condition.getExpressionList();
        Class<?> springBean = condition.getBeanClass();
        AssertExceptionCondition ifErrorCondition = condition.getIfError();
        groupList.forEach(expression -> expressionParser.parseExpression(method, args, expression, springBean, ifErrorCondition));
    }

    /**
     * 将切入点方法上的注解信息封装
     */
    private List<AssertAspectCondition> buildCondition(BatchAssert batchAssert, BaseAssert baseAssert) {
        List<AssertAspectCondition> spElPermissionGroup = new ArrayList<>();

        // 优先解析BaseAssert (更细粒度)
        if (baseAssert != null) {
            Assert.notEmpty(baseAssert.value(), "权限列表不能为空");
            spElPermissionGroup.add(this.parseBaseAssertAnnotation(baseAssert));
        } else {
            Assert.notEmpty(batchAssert.value(), "权限列表不能为空");

            for (BaseAssert baseAssert_ : batchAssert.value()) {
                spElPermissionGroup.add(this.parseBaseAssertAnnotation(baseAssert_));
            }
        }

        return spElPermissionGroup;
    }

    /**
     * 解析注解 BaseAssert 中的内容
     */
    private AssertAspectCondition parseBaseAssertAnnotation(BaseAssert baseAssert) {
        List<String> spELStringList = new ArrayList<>(baseAssert.value().length);
        for (String permissionEl : baseAssert.value()) {
            Assert.hasText(permissionEl, "permission spEl must be not blank");
            spELStringList.add(permissionEl);
        }

        // 包装成VO也可以,放在ThreadLocal上下文中也行
        AssertAspectCondition condition = new AssertAspectCondition();
        condition.setExpressionList(spELStringList);
        condition.setBeanClass(baseAssert.validatorBeanClass());
        AssertExceptionCondition exceptionCondition = new AssertExceptionCondition();
        exceptionCondition.setIfError(baseAssert.ifError()[0]);
        exceptionCondition.setErrorMsg(baseAssert.errorMsg());
        exceptionCondition.setAssertType(baseAssert.assertType());
        condition.setIfError(exceptionCondition);
        return condition;
    }
}
